/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package beantown;

import simframe.simulation.*;
import simframe.util.*;


/**
 *
 * @author Steve Noble
 */
public class Sim extends Simulation {

    private final LocFeeMarket market;
    private Swarm<Producer> firms = new Swarm<Producer>();
    private Swarm<Consumer> cons = new Swarm<Consumer>();
 
    // define actions as runnable methods
    static final Long morning = 6 * Time.Hour;
    static final Long noon = 12 * Time.Hour;
    static final Long afternoon = 15 * Time.Hour;
    static final Long evening = 18 * Time.Hour;
    
    static final Time.Next daily = new Time.Next() {
        public long getNext(long t) {
           return t + Time.Day;
        }
    };
    
    public Sim(int numConsumers, int numFirms, double w, double h) {

        // create objects
        market = new LocFeeMarket();

          int ii;
        double dimX = w;
        double dimY = h;

        for (ii = 0; ii < numFirms; ++ii) {
            double x = (5 + Math.random() * (dimX - 10));
            double y = (5 + Math.random() * (dimY - 10));

            Location loc = new Location(x, y);

            Producer firm = new Producer(market, loc);

            firms.add(firm);
        }

        for (ii = 0; ii < numConsumers; ++ii) {
            double x = (5 + Math.random() * (dimX - 10));
            double y = (5 + Math.random() * (dimY - 10));
            Location loc = new Location(x, y);

            Consumer con = new Consumer(market, loc);

            Producer firm = firms.selectRandom();

            firm.owners.add(con);

            cons.add(con);
        }

        // create simulation threads

        // consumer thread
        cons.addInitSwarmAction(Consumer.shop, noon, daily);
        cons.addInitSwarmAction(Consumer.getDole, evening, daily);
        cons.addInitSwarmAction(Consumer.updateHealth, morning, daily);

        // firms
        firms.addInitSwarmAction(Producer.createOffers, morning, daily);
        firms.addInitSwarmAction(Producer.processDividends, evening, daily);

        // markets
        newThread().addSimObject(cons);
        newThread().addSimObject(firms);
        newThread().addSimObject(market);

        createMetrics();
    }

    // These should be replaced by
    // more generic parameter changing methods
    public synchronized void updateMargin(double val) {
        for (Producer firm : firms) {
            firm.updateMargin(val);
        }
    }

    public synchronized double updateDole(double val) {

        double aveDole = 0.0;
        for (Consumer con : cons) {
            aveDole += con.updateDole(val);
        }

        aveDole /= cons.size();

        return aveDole;
    }

    private void createMetrics() {
   
        // dollars per firm
        Metric dollarsPerFirm = new Metric("Firm$", Metric.Units.Dollars,
                new  Metric.Compute() {

                    public double exec() {
                        double rval = 0;

                        double num = 0;

                        for (Producer firm : firms) {
                            if (!firm.dead()) {
                                rval += firm.getSavings();
                                num += 1;
                            }
                        }

                        if (num > 0) {
                            rval /= num;
                        }

                        return rval;
                    }
                });

        dollarsPerFirm.setRange(0, 1000);
        addMetric(dollarsPerFirm);

        // dollars per consumer
        Metric dollarsPerCons = new Metric("People$", Metric.Units.Dollars,
                new  Metric.Compute() {

                    public double exec() {
                        double rval = 0;

                        double num = 0;

                        for (Consumer con : cons) {
                            if (!con.dead()) {
                                rval += con.getSavings();
                                num += 1;
                            }
                        }

                        if (num > 0) {
                            rval /= num;
                        }

                        return rval;
                    }
                });

        dollarsPerCons.setRange(0, 1000);
        addMetric(dollarsPerCons);


        // health per consumer
        Metric healthPerCons = new Metric("Health", Metric.Units.Num,
                new  Metric.Compute() {

                    public double exec() {
                        double rval = 0;

                        double num = 0;

                        for (Consumer con : cons) {
                            if (!con.dead()) {
                                rval += con.getUtility();
                                num += 1;
                            }
                        }

                        if (num > 0) {
                            rval /= num;
                        }

                        return rval;
                    }
                });

        healthPerCons.setRange(0, Consumer.healthMax);
        addMetric(healthPerCons);

        // num consumer
        Metric numCons = new Metric("Num People", Metric.Units.Num,
                new  Metric.Compute() {

                    public double exec() {
                        double rval = 0;

                        for (Consumer con : cons) {
                            if (!con.dead()) {
                                rval += 1;
                            }
                        }

                        return rval;
                    }
                });


        numCons.setRange(0, cons.size());
        addMetric(numCons);

    }
}
